package com.yunchao.library.socket.metrics

import com.yunchao.library.socket.config.Message
import com.yunchao.library.socket.config.MessageHeader
import com.yunchao.library.socket.enum.ConnectionEvent
import com.yunchao.library.socket.logger.SocketLogger
import java.util.Date
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.math.log10
import kotlin.math.pow
import kotlin.math.roundToInt

class BasicSocketMetrics : SocketMetrics {
    private val messagesSent = AtomicInteger(0)
    private val messagesReceived = AtomicInteger(0)
    private val bytesSent = AtomicLong(0)
    private val bytesReceived = AtomicLong(0)
    private val chunksSent = AtomicInteger(0)
    private val chunksReceived = AtomicInteger(0)
    private val chunkedBytesSent = AtomicLong(0)
    private val chunkedBytesReceived = AtomicLong(0)
    private val connectionAttempts = AtomicInteger(0)
    private val connectionSuccesses = AtomicInteger(0)
    private val connectionFailures = AtomicInteger(0)
    private val errorCounts = mutableMapOf<String, AtomicInteger>()


    override fun recordMessageSent(header: MessageHeader, bodyBytes: ByteArray) {
        messagesSent.incrementAndGet()
        bytesSent.addAndGet(header.bodyLength.toLong())
        SocketLogger.logMessage("SEND", header, bodyBytes)
    }

    override fun recordMessageReceived(message: Message) {
        if (message.header.isChunked) {
            chunksReceived.incrementAndGet()
            chunkedBytesReceived.addAndGet(message.header.bodyLength.toLong())
        } else {
            messagesReceived.incrementAndGet()
            bytesReceived.addAndGet(message.header.bodyLength.toLong())
        }
        SocketLogger.logMessage("RECEIVE", message)
    }

    override fun recordChunkSent(offset: Int, chunkSize: Int, bodyBytes: ByteArray) {
        chunksSent.incrementAndGet()
        chunkedBytesSent.addAndGet(bodyBytes.size.toLong())
        SocketLogger.logOperation(
            "Sent offset = $offset of chunkSize = $chunkSize  total = ${bodyBytes.size} bytes"
        )
    }

    override fun recordConnectionEvent(event: ConnectionEvent) {
        SocketLogger.logConnectionState(event)
        when (event) {
            is ConnectionEvent.Connecting -> {
                connectionAttempts.incrementAndGet()
            }

            is ConnectionEvent.Connected -> {
                connectionSuccesses.incrementAndGet()
            }

            is ConnectionEvent.Disconnected -> {
                connectionFailures.incrementAndGet()
            }

            is ConnectionEvent.Reconnecting -> {
                connectionAttempts.incrementAndGet()
            }

            is ConnectionEvent.Error -> {
                connectionFailures.incrementAndGet()
            }
        }
    }

    fun recordError(errorType: String) {
        errorCounts.computeIfAbsent(errorType) { AtomicInteger(0) }.incrementAndGet()
    }

    fun getStats(): SocketStats {
        return SocketStats(
            messagesSent = messagesSent.get(),
            messagesReceived = messagesReceived.get(),
            bytesSent = bytesSent.get(),
            bytesReceived = bytesReceived.get(),
            chunksSent = chunksSent.get(),
            chunksReceived = chunksReceived.get(),
            chunkedBytesSent = chunkedBytesSent.get(),
            chunkedBytesReceived = chunkedBytesReceived.get(),
            connectionAttempts = connectionAttempts.get(),
            connectionSuccesses = connectionSuccesses.get(),
            connectionFailures = connectionFailures.get(),
            errorCounts = errorCounts.mapValues { it.value.get() },
            timestamp = System.currentTimeMillis()
        )
    }

    fun reset() {
        messagesSent.set(0)
        messagesReceived.set(0)
        bytesSent.set(0)
        bytesReceived.set(0)
        chunksSent.set(0)
        chunksReceived.set(0)
        chunkedBytesSent.set(0)
        chunkedBytesReceived.set(0)
        connectionAttempts.set(0)
        connectionSuccesses.set(0)
        connectionFailures.set(0)
        errorCounts.clear()
    }

    data class SocketStats(
        val messagesSent: Int,
        val messagesReceived: Int,
        val bytesSent: Long,
        val bytesReceived: Long,
        val chunksSent: Int,
        val chunksReceived: Int,
        val chunkedBytesSent: Long,
        val chunkedBytesReceived: Long,
        val connectionAttempts: Int,
        val connectionSuccesses: Int,
        val connectionFailures: Int,
        val errorCounts: Map<String, Int>,
        val timestamp: Long
    ) {
        private fun connectionSuccessRate(): Double {
            val total = connectionAttempts
            return if (total > 0) connectionSuccesses.toDouble() / total else 0.0
        }

        private fun averageSentMessageSize(): Int {
            return if (messagesSent > 0) (bytesSent.toDouble() / messagesSent).roundToInt() else 0
        }

        private fun chunkedTransferRatio(): Double {
            val totalBytes = bytesSent + bytesReceived
            val chunkedBytes = chunkedBytesSent + chunkedBytesReceived
            return if (totalBytes > 0) chunkedBytes.toDouble() / totalBytes else 0.0
        }

        override fun toString(): String {
            return """
            |==== Socket 统计报告 ====
            |时间: ${Date(timestamp)}
            |
            |== 消息统计 ==
            |发送消息数: $messagesSent (${bytesSent.toHumanReadableSize()})
            |接收消息数: $messagesReceived (${bytesReceived.toHumanReadableSize()})
            |平均发送大小: ${averageSentMessageSize().toHumanReadableSize()}
            |
            |== 分块传输 ==
            |分块发送: $chunksSent (${chunkedBytesSent.toHumanReadableSize()})
            |分块接收: $chunksReceived (${chunkedBytesReceived.toHumanReadableSize()})
            |分块比例: ${"%.1f".format(chunkedTransferRatio() * 100)}%
            |
            |== 连接统计 ==
            |连接尝试: $connectionAttempts
            |成功连接: $connectionSuccesses
            |连接失败: $connectionFailures
            |成功率: ${"%.1f".format(connectionSuccessRate() * 100)}%
            |
            |== 错误统计 ==
            |${errorCounts.entries.joinToString("\n") { "  ${it.key}: ${it.value}" }}
            |=========================
            """.trimMargin()
        }
    }
}

private fun Long.toHumanReadableSize(): String {
    if (this <= 0) return "0 B"
    val units = arrayOf("B", "KB", "MB", "GB", "TB")
    val digitGroups = (log10(toDouble()) / log10(1024.0)).toInt()
    return "%.1f %s".format(this / 1024.0.pow(digitGroups.toDouble()), units[digitGroups])
}

private fun Int.toHumanReadableSize(): String = toLong().toHumanReadableSize()